package cjh.com.myiocannotation;

/*
 * author: caojunhao
 * date  : 2020/11/25
 * desc  :
 *
 *
 * 动态代理：
 * 1.传入被代理对象得到代理对象的实例
 * 2.通过调用代理类的方法进而调用被代理类的方法
 */

import android.util.Log;
import android.view.View;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import cjh.com.myiocannotation.annotation.ContentView;
import cjh.com.myiocannotation.annotation.EventBase;
import cjh.com.myiocannotation.annotation.ViewInject;


/*
* 1.获取class对象
*       Class<?> clazz = context.getClass();
* 2.根据class对象拿到指定的注解类
*       ContentView contentView = clazz.getAnnotation(ContentView.class);
* 3.获取当前注解类的值
*       int layoutID = contentView.value();
* 4.根据class对象拿到要执行的方法
*       Method method= clazz.getMethod("setContentView", int.class);
* 5.通过反射执行方法
*       method.invoke(context, layoutID);
*
*
* */

public class InjectUtils {
    private static final String TAG="InjectUtils";
    public static void inject(Object context) {
        //注入的功能在这里完成
        injectLayout(context);
        injectView(context);
        injectClick(context);
    }

    /**
     * 布局
     *
     * @param context
     */
    private static void injectLayout(Object context) {
        //得到clazz对象
        Class<?> clazz = context.getClass();
        //Log.d(TAG, "injectLayout:clazz==="+clazz);
        //根据clazz获取到ContentView类的注解信息
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        //拿到ContentView注解的值
        if(contentView!=null){
            int layoutID = contentView.value();
            try {
                //获取指定的方法--参数1 ：指明获取的方法的名称  参数2：指明获取的方法的形参列表
                Method method= clazz.getMethod("setContentView", int.class);
                //进行反射执行方法-this.setContentView(R.layout.main_layout)
                //参数1：方法的调用者this  参数2：给方法形参赋值的实参R.layout.main_layout
                method.invoke(context, layoutID);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 控件注入
     *
     * @param context
     */
    private static void injectView(Object context) {
        Class<?> clazz = context.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //Log.d(TAG, "injectLayout:field==="+field);
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                int valueId = viewInject.value();
                try {
                    //  btn=this.findViewById(R.id.btn_test);
                    Method method = clazz.getMethod("findViewById", int.class);
                    View view = (View) method.invoke(context, valueId);
                    //2.保证当前属性是可访问的,不设置是无法修改属性值的
                    field.setAccessible(true);
                    //获取、设置指定对象的此属性值
                    //name.set(p,"Tom");
                    field.set(context, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 事件
     * 涉及事件三要素：事件源、事件、事件处理
     *
     * @param context
     */
    private static void injectClick(Object context) {
        Class<?> clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Log.d(TAG, "injectLayout:method==="+method);
            //这就写死了
            //OnClick annotation = method.getAnnotation(OnClick.class);
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Log.d(TAG, "injectLayout:annotation==="+annotation);
                //得到OnClick上一级的EventBase
                //annotionClass==cjh.com.myiocannotation.annotation.OnClick
                Class<?> annotionClass = annotation.annotationType();
                Log.d(TAG, "injectLayout:annotionClass==="+annotionClass);
                //OnClick上的注解是EventBase
                EventBase eventBase = annotionClass.getAnnotation(EventBase.class);
                //如果没有EventBase则当前方法不是一个事件处理程序
                if (eventBase == null) {
                    continue;
                }
                //否则是事件处理回调的函数，是的开始得到相关的事件要素
                //1.订阅关系
                String listenerSetter = eventBase.listenerSetter();
                //2.事件
                Class<?> listenerType = eventBase.listenerType();
                //3.事件处理程序
                String callbackMethod = eventBase.callbackMethod();
                try {
                    //得到OnClick注解上面的值.但是这样的话OnLongClick就无法获取，不是动态了
//                    OnClick onClick = method.getAnnotation(OnClick.class);
//                    int[] viewId = onClick.value();

                    //获取注解上面的值，也就是OnClick和OnLongClick上面的注解的值
                    Method valueMethod = annotionClass.getDeclaredMethod("value");
                    Log.d(TAG, "injectLayout:valueMethod==="+valueMethod);
                    int[] viewId = (int[]) valueMethod.invoke(annotation);

                    //对每一个ID进行事件绑定
                    for (int id : viewId) {
                        //找到ID对应的对象-findViewById
                        Method findId = clazz.getMethod("findViewById", int.class);
                        View view = (View) findId.invoke(context, id);
                        if (view == null) {
                            continue;
                        }


//                        btn.setOnClickListener(new View.OnClickListener() {
//                            @Override
//                            public void onClick(View v) {
//
//                            }
//                        });

                        //得到ID对应的View以后,开始在View上执行监听
                        //用代理执行View.OnClickListener()方法
                        //需要执行activity上的OnClick方法
                        //被代理对象：context
                        //method=public void cjh.com.myiocannotation.MainActivity.onClick(android.view.View)
                        ListenerInvocationHandler listenerHandler = new ListenerInvocationHandler(context, method);
                        Log.d(TAG, "injectLayout:listenerHandler method==="+method);

                        //代理这个接口View.OnClickListener() == listenerType
                        //得到代理对象proxy
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerHandler);
                        //执行方法view.setOnClickListener
                        //proxy就是代理new View.OnClickListener(){}
                        Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        onClickMethod.invoke(view, proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
